/**
 * WTF Engine
 * 
 * License... etc.
 **
 * Widgets Implementation (Woosta GUI Toolkit).
 * 
 * Base Widget also contains implementation of rounded corners!
 **
 * Authors: Mac Duy Hai, Sean Micklethwaite
 */

#include "gui_widgets.h"
#include <iostream>

using namespace WtfGui;
using namespace WtfEngine;

#define WINDOW_PADDING	1
#define OVERLAY_NEXT	glTranslatef(0, 0, 0.00001)
#define INPUTFIELD_MAXHISTORY 10

// Rounded corners
#define CORNER_RADIUS		8
#define CORNER_RESOLUTION	8

// Some keypress events
#define KBD_CTRL_W		23
#define KBD_CTRL_P		16
#define KBD_CTRL_N		14
#define KBD_CTRL_A		 1
#define KBD_CTRL_S		19
#define KBD_CTRL_D		 4

#define FOREACH_AXIS(i) for(int i = 0; i < 2; i++)


AbstractWidget::AbstractWidget(): mpDelegate(this) {
};

//
// Base Widget
//



Widget::Widget(const String& sName)
: GcObject(), mrLayout(new WidgetLayout<>()), mpParent(NULL), mDragDropType(0), mIsDragging(false) {
	mrBackground = tBackground::Get("v_gui_bg");
	mFgColour = DataManager::GetInstance().Load<Colour>("v_gui_fgColour");
};

Widget::Widget(const String& sName, iVector2D vPosition, iVector2D vSize)
	: GcObject(), mrLayout(new WidgetLayout<>()), mpParent(NULL), mDragDropType(0), mIsDragging(false) {
	Move(vPosition);
	Resize(vSize);
	mrBackground = tBackground::Get("v_gui_bg");
	mFgColour = DataManager::GetInstance().Load<Colour>("v_gui_fgColour");
};

void Widget::Remove() {
	if(mpParent)
		mpParent->RemoveChild(this);
	else
		Renderer::GetInstance().RemoveOverlay(this);
};


///
/// Push a position coordinate matrix, so everything can be drawn relative to it
///
void Widget::Begin() {
	glPushMatrix();
	glTranslatef(getRealPosition()[0], -getRealPosition()[1], 0);
}

///
/// Paint function, specifying how the Widget should be displayed.
/// This function should be implemented manually by all derived Widgets,
/// but may call this function to draw the background.
///
void Widget::Paint(float d) {
	mrBackground->Value->Render(getRealSize());
	
	// Translate to accommodate for padding
	IWidgetLayout::tPadding Padding = getPadding();
	glTranslatef(Padding[0][0], -Padding[0][1], 0);
	//SetupClipping();
}

///
/// Pops out the position coordinate
void Widget::End() {
	glPopMatrix();
};

void Widget::SetupClipping() const {
	if(mpParent) {
		IWidgetLayout::tPadding Padding = getPadding();
		glPushMatrix();
		glTranslatef(-(Padding[0][0] + getRealPosition()[0]), -(getRealPosition()[1] - Padding[0][1]), 0);
		mpParent->SetupClipping();
		glPopMatrix();
	} else {
		glClearStencil(2);
		glClear(GL_STENCIL_BUFFER_BIT);
	};

	// Setup stencil buffer
	iVector2D vSize = getRealSize() - (getPadding2D<0>() + getPadding2D<1>());

	glColor4f(0,0,0,0);

	// We can draw in all the pixels equal to 2. If the pixel is 2 and in our region too,
	// set it to 1 to indicate overlap.
	glStencilFunc(GL_EQUAL, 2, 3);
	glStencilOp(GL_KEEP, GL_DECR, GL_DECR);
	glBegin(GL_QUADS);
		glVertex2f(0, 0);
		glVertex2f(vSize.x(), 0);
		glVertex2f(vSize.x(), -vSize.y());
		glVertex2f(0, -vSize.y());
	glEnd();

	// The overlapping region is drawable, set all 1s to 2.
	glStencilFunc(GL_EQUAL, 1, 3);
	glStencilOp(GL_ZERO, GL_INCR, GL_INCR);
	glBegin(GL_QUADS);
		glVertex2f(-100000, -100000);
		glVertex2f(100000, -100000);
		glVertex2f(100000, 100000);
		glVertex2f(-100000, 100000);
	glEnd();

	// Setup for drawing
	glStencilFunc(GL_EQUAL, 2, 3);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
};


/**
 * Sets the foreground colour to the specified colour, with optional
 * interpolation.
 **/
void Widget::SetFgColour(const Colour::tRef& colour, bool bInterpolate) {
	if(bInterpolate){
		mFgColour = new Colour("colour fade", mFgColour->Value.Value);
		mFgColour->Value.Init(colour->Value.Value, (new ObjFunctor<Widget, void, const Colour::tRef&>(this, &Widget::_SetFgColour))->Bind(colour));
	} else {
		mFgColour = colour;
	};
};

/**
 * Returns true if the widget currently has focus.
 **/
bool Widget::isSelected() {
	return (mpParent && (mpParent->mrActiveChild == this) && mpParent->isSelected());
};


void Widget::OnResize() {
	if(mpParent) {
		mpParent->OnChildResize();
	};
};

/**
 * For drag-drop enabled widgets, this starts the dragging of the widget.
 **/
bool Widget::OnMouseDown(const iVector2D& vPos, WtfEngine::tMouseButton btn){
	if(mDragDropType){
		mIsDragging = true;
		return true;
	} else {
		return false;
	};
};

/**
 * For drag-drop enabled widgets, this ends dragging.
 * @todo	Detect what the widget has been dropped on, and
 * 			perform appropriate actions.
 **/
bool Widget::OnMouseUp(const iVector2D& vPos, WtfEngine::tMouseButton btn){
	if(mDragDropType){
		mIsDragging = false;
		return true;
	} else {
		return false;
	}
};

bool Widget::OnMouseOver(const iVector2D &vPos, const iVector2D &vDelta) {
	return false;
};

bool Widget::OnMouseOut(const iVector2D &vPos, const iVector2D &vDelta) {
	return false;
};

/**
 * For drag-drop enabled widgets, this moves the widget with the mouse when
 * dragging.
 **/
bool Widget::OnMouseMove(const iVector2D& vPos, const iVector2D& vDelta){
	if(mIsDragging){
		Move(getPosition() + vDelta);
		return true;
	} else {
		return false;
	};
};


/**** Widget Backgrounds ****/

Widget::ColourBackground::ColourBackgroundCreationOptions::ColourBackgroundCreationOptions()
: rColour(DataManager::GetInstance().Load<Colour>("v_gui_bgColour")) {
};

void Widget::ColourBackground::Render(const iVector2D &vSize) {
	glColor4fv(mrColour->Value.Value.toArray());
	glBegin(GL_QUADS);
		glVertex2f(0,0);
		glVertex2f(vSize.x(), 0);
		glVertex2f(vSize.x(), -vSize.y());
		glVertex2f(0, -vSize.y());
	glEnd();
};


Widget::RoundedCornerBackground::RoundedCornerBackgroundCreationOptions::RoundedCornerBackgroundCreationOptions()
: vRadii(iVector2D(8, 8), iVector2D(8, 8)) {
};

void Widget::RoundedCornerBackground::Render(const iVector2D& vSize) {
	int i;

	if(mrColour->Value.Value[3] != 0.0){

		//
		// Non-transparent background, so draw the background, complete with
		// rounded corners
		//
		
		glColor4fv(mrColour->Value.Value.toArray());
		glPushMatrix();
		
		int bx = mvRadii[0][0] + mvRadii[0][1];
		int by = mvRadii[1][0] + mvRadii[1][1];

		// inner filling
		glTranslatef(mvRadii[0][0], -mvRadii[1][0], 0);
		glBegin(GL_QUADS);
			glVertex2f(0,0);
			glVertex2f(vSize.x() - bx, 0);
			glVertex2f(vSize.x() - bx, -vSize.y() + by);
			glVertex2f(0, -vSize.y() + by);
		glEnd();
		
		// upper left corner
		glBegin(GL_TRIANGLE_FAN);
			glVertex2f(0,0);
			for (i = 0; i < CORNER_RESOLUTION; i++) 
				glVertex2f( -mvRadii[0][0] * cos(M_PI/2*i/CORNER_RESOLUTION),
							mvRadii[1][0] * sin(M_PI/2*i/CORNER_RESOLUTION));
			glVertex2f(0, mvRadii[1][0]);
			glVertex2f(vSize.x() - bx, mvRadii[1][0]);
			glVertex2f(vSize.x() - bx, 0);
		glEnd();
		
		// upper right corner
		glTranslatef(vSize.x() - bx, 0, 0);
		glBegin(GL_TRIANGLE_FAN);
			glVertex2f(0,0);
			for (i = 0; i < CORNER_RESOLUTION; i++) 
				glVertex2f( mvRadii[0][1] * sin(M_PI/2*i/CORNER_RESOLUTION),
							mvRadii[1][0] * cos(M_PI/2*i/CORNER_RESOLUTION));
			glVertex2f(mvRadii[0][1], 0);
			glVertex2f(mvRadii[0][1], -vSize.y() + by);
			glVertex2f(			   0, -vSize.y() + by);
		glEnd();
		
		// lower right corner
		glTranslatef(0, -vSize.y() + by, 0);
		glBegin(GL_TRIANGLE_FAN);
			glVertex2f(0,0);
			for (i = 0; i < CORNER_RESOLUTION; i++)
				glVertex2f(  mvRadii[0][1] * cos(M_PI/2*i/CORNER_RESOLUTION),
							-mvRadii[1][1] * sin(M_PI/2*i/CORNER_RESOLUTION));
			glVertex2f(0, -mvRadii[1][1]);
			glVertex2f(-vSize.x() + 2 * CORNER_RADIUS, -CORNER_RADIUS);
			glVertex2f(-vSize.x() + 2 * CORNER_RADIUS, 0);
		glEnd();
		
		// lower left corner
		glTranslatef(-vSize.x() + bx, 0, 0);
		glBegin(GL_TRIANGLE_FAN);
			glVertex2f(0,0);
			for (i = 0; i < CORNER_RESOLUTION; i++)
				glVertex2f( -mvRadii[0][0] * sin(M_PI/2*i/CORNER_RESOLUTION),
							-mvRadii[1][1] * cos(M_PI/2*i/CORNER_RESOLUTION));
			glVertex2f(-mvRadii[0][0], 0);
			glVertex2f(-mvRadii[0][0], vSize.y() - by);
			glVertex2f(				0, vSize.y() - by);
		glEnd();
	
		// end
		glPopMatrix();
	};
};


/**** Widget Container ****/

#define OVERLAP(rW, vPos) ((rW)->getRealPosition()[0] <= vPos[0] && \
	((rW)->getRealPosition()[0] + (rW)->getRealSize()[0]) > vPos[0] && \
	(rW)->getRealPosition()[1] <= vPos[1] && ((rW)->getRealPosition()[1] + (rW)->getRealSize()[1]) > vPos[1])

void WidgetContainer::AddChild(const AbstractWidget::tRef& rChild){
	// TODO: multiple parents?
	mChildren.push_back(rChild);
	
	rChild->OnInsert(this);

	if(mrActiveChild.isNull())
		Select(rChild, false);
};

void WidgetContainer::RemoveChild(const AbstractWidget::tRef& rChild){
	mChildren.remove(rChild);
	if(mrActiveChild == rChild){
		if(mChildren.empty())
			mrActiveChild = Widget::tRef();
		else
			Select(*mChildren.begin(), false);
	};
};

bool WidgetContainer::HasChild(const AbstractWidget::tRef& rChild){
	tChildList::iterator i;
	
	for(i = mChildren.begin(); i != mChildren.end(); i++){
		if(*i == rChild) {
			return true;
		};
	};

	return false;
};

void WidgetContainer::SendToFront(const AbstractWidget::tRef& rChild){
	mChildren.remove(rChild);
	mChildren.push_front(rChild);
};

void WidgetContainer::SendToBack(const AbstractWidget::tRef& rChild){
	mChildren.remove(rChild);
	mChildren.push_back(rChild);
};

void WidgetContainer::Paint(float d) {
	tChildList::reverse_iterator i;
	
	// Draw background, etc.
	Widget::Paint(d);
	
	// Draw child widgets
	for(i = mChildren.rbegin(); i != mChildren.rend(); i++){
		if((*i)->getRealPosition()[0] < getInnerSize()[0]
			&& (*i)->getRealPosition()[1] < getInnerSize()[1]) {
			//d = d / 2;
			//glTranslatef(0, 0, d);
			//SetupClipping();
			(*i)->Draw(d / 2);
		};
	};
};


/**
 * Marks the specified widget as selected in this container, optionally
 * propagating the selection to give the widget full focus.
 **/
void WidgetContainer::Select(const AbstractWidget::tRef& rChild, bool bPropagate) {
	if(mrActiveChild == rChild){
		
		//
		// The child is already selected in this container. If requested,
		// propagate the selection up the tree, hence (possibly) selecting
		// this container too, which will cause an OnSelect event to
		// propagate back down to the child.
		//
		
		if(bPropagate && mpParent)
			mpParent->Select(this, true);
	}else{
		if(!mrActiveChild.isNull())
			mrActiveChild->OnDeselect();
		
		mrActiveChild = rChild;
		
		if(bPropagate && mpParent){
			if(isSelected()) {
				
				//
				// This container is already selected, no OnSelect will
				// propagate back down to the child. Call OnSelect here.
				//
				
				rChild->OnSelect();
			} else {
				mpParent->Select(this, true);
			};
		} else if(isSelected()) {
			
			//
			// Propagation up the tree not allowed, so the child element
			// has only become selected if this container is selected itself.
			//
			
			rChild->OnSelect();
		};
	};
};

iVector2D WidgetContainer::getContentSize() {
	iVector2D vSize, vChildSize, vChildPos;
	tChildList::iterator i;
	
	for(i = mChildren.begin(); i != mChildren.end(); i++){
		vChildSize = (*i)->getSize();
		vChildPos = (*i)->getPosition();
		FOREACH_AXIS(iAxis) {
			vSize[iAxis] = maxf(vSize[iAxis], vChildSize[iAxis] + vChildPos[iAxis]);
		};
	};

	return vSize;
};

void WidgetContainer::OnResize() {
	// Send parent resize event to all children
	tChildList::iterator i;
	
	for(i = mChildren.begin(); i != mChildren.end(); i++){
		(*i)->OnParentResize();
	};
};

void WidgetContainer::OnSelect() {
	if(!mrActiveChild.isNull())
		mrActiveChild->OnSelect();
};

void WidgetContainer::OnDeselect() {
	if(!mrActiveChild.isNull())
		mrActiveChild->OnDeselect();
};

bool WidgetContainer::OnKeyPress(int key, int flags){
	if(!mrActiveChild.isNull()) {
		if(mrActiveChild->OnKeyPress(key, flags)) {
			return true;
		};
	};

	switch(key){
		case '\t':
			if(flags & KMOD_SHIFT)
				PreviousChild<IInput>();
			else
				NextChild<IInput>();
			return true;
	};

	return false;
};

bool WidgetContainer::OnMouseDown(const iVector2D& vPos, tMouseButton btn){
	iVector2D	vChPos(vPos - getPadding()[0]);
	AbstractWidget::tRef r = GetChildAtPosition(vChPos);
	
	if(!r.isNull()){
		return r->OnMouseDown(vChPos - r->getRealPosition(), btn);
	} else {
		return Widget::OnMouseDown(vPos, btn);
	};
};

bool WidgetContainer::OnMouseUp(const iVector2D& vPos, tMouseButton btn){
	iVector2D	vChPos(vPos - getPadding()[0]);
	AbstractWidget::tRef r = GetChildAtPosition(vChPos);
	if(!r.isNull()) {
		return r->OnMouseUp(vChPos - r->getRealPosition(), btn);
	} else {
		return Widget::OnMouseUp(vPos, btn);
	};
};

bool WidgetContainer::OnMouseMove(const iVector2D& vPos, const iVector2D& vDelta){
	iVector2D	vChPos(vPos - getPadding()[0]);
	AbstractWidget::tRef r((mpParent && (mpParent->GetChildAtPosition(vPos + getRealPosition()) == this))
			? GetChildAtPosition(vChPos) : Widget::tRef());
	AbstractWidget::tRef r2(GetChildAtPosition(vChPos - vDelta));
	
	bool bHandled = false;

	if(!r2.isNull() && (r != r2)) {
		
		//
		// Mouse moved out of child widget - call OnMouseOut
		//
		
		r2->OnMouseOut(vChPos - r2->getRealPosition(), vDelta);
		r2->OnMouseMove(vChPos - r2->getRealPosition(), vDelta);
	};
	
	if(!r.isNull()){
		
		//
		// Mouse moved over a child widget - call OnMouseOver
		//
		
		r->OnMouseOver(vChPos - r->getRealPosition(), vDelta);
		r->OnMouseMove(vChPos - r->getRealPosition(), vDelta);
		bHandled = true;
	};
	
	return (Widget::OnMouseMove(vPos, vDelta) || bHandled);
};

AbstractWidget::tRef WidgetContainer::GetChildAtPosition(const iVector2D& vPos){
	tChildList::iterator i;
	for(i = mChildren.begin(); i != mChildren.end(); i++) {
		if(OVERLAP(*i, vPos)) {
			return *i;
		};
	};
	
	return AbstractWidget::tRef();
};


//////
////// WIDGETS
//////
using namespace WtfGui::Widgets;

//
// Label
//

Label::Label(const String& caption, const tFont::tRef& font, bool transparent)
	: Widget("Label"), mFont(font) {
	this->mCaption = caption;
	this->mTransparent = transparent;
	ExpandToText();
}

Label::~Label() {
	
}

void Label::Paint(float d) {
	if (!mTransparent) Widget::Paint(d);
	mFont->Value->DrawText(mCaption, getInnerSize(), IFont::ALIGN_LEFT);
}

void Label::ExpandToText() {
	setInnerSize(mFont->Value->GetSize(mCaption, 0));
};


//
// Button
//

bool Button::OnMouseUp(const iVector2D& vPos, WtfEngine::tMouseButton btn) {
	if(btn == WtfEngine::MOUSE_LEFT) {
		if(!mrOnClick.isNull()) (*mrOnClick)();
		return true;
	} else {
		return Label::OnMouseUp(vPos, btn);
	};
};

bool Button::OnKeyPress(int key, int flags) {
	if(key == '\r'){
		if(!mrOnClick.isNull()) (*mrOnClick)();
		return true;
	} else {
		return Label::OnKeyPress(key, flags);
	};
};


//
// InputField
//

InputField::InputField(const tFont::tRef& font) : Widget("Input Field"), mFont(font), mHistoryPos(0) {
	buffer = new Utils::StringBuffer(100);
};

InputField::~InputField() {
};

bool InputField::OnKeyPress(int key, int flags) {
	switch (key) {
		case '\b':
			buffer->deleteChar();
			break;
		case InputKey::DOWN:	// Next history item (newer)
			if(mHistoryPos == 2) {
				mHistory[mHistory.size() - mHistoryPos] = buffer->toString();
				buffer->setText(mHistory[mHistory.size() - 1]);
				mHistory.pop_back();
				mHistoryPos = 0;
			} else if (mHistoryPos > 0) {
				mHistory[mHistory.size() - mHistoryPos] = buffer->toString();
				mHistoryPos--;
				buffer->setText(mHistoryPos <= 0 ? "" : mHistory[mHistory.size() - mHistoryPos]);
			};
			
			break;
		case InputKey::UP:	// Previous history item (older)
			if(mHistoryPos < mHistory.size()){
				mHistoryPos++;
				if(mHistoryPos == 1) {
					mHistory.push_back(buffer->toString());
					mHistoryPos++;
				} else {
					mHistory[mHistory.size() - (mHistoryPos - 1)] = buffer->toString();
				};
				
				buffer->setText(mHistoryPos <= 0 ? "" : mHistory[mHistory.size() - mHistoryPos]);
			};
			
			break;
		case InputKey::RIGHT:	// move cursor right
			buffer->moveCursor(1);
			break;
		case InputKey::LEFT:	// move cursor left
			buffer->moveCursor(-1);
			break;
		default:
			buffer->insertChar((char)key);
			break;
	};

	return true;
};

bool InputField::OnMouseDown(const iVector2D &vPos, WtfEngine::tMouseButton btn) {
	getParent()->Select(this, true);
	return Widget::OnMouseDown(vPos, btn);
};

void InputField::PushHistory() {
	if(mHistoryPos > 0) mHistory.pop_back();
	if((mHistory.empty() || (mHistory[mHistory.size() - 1] != buffer->toString()))
		   && (strlen(buffer->toString()) > 0))
		mHistory.push_back(buffer->toString());
	mHistoryPos = 0;
}

void InputField::Paint(float d) {
	Widget::Paint(d);
	
	// Draw Text (with cursor, hence the "true") - add some padding
	OVERLAY_NEXT;
	glTranslatef(4.0, 0.0, 0.0);
	mFont->Value->DrawText(buffer->toString(true), getRealSize(), IFont::ALIGN_LEFT);
};

//
// LogField
//

LogField::LogField(int maxlines, const tFont::tRef& font) : Widget("Log Field"), mFont(font) {
	this->mMaxLines = maxlines;
};

LogField::~LogField() {
	this->Clear();
};

void LogField::Dump(const String& message) {
	if (mLogRoll.size() > mMaxLines) {
		mLogRoll.erase(mLogRoll.begin());
	}
	mLogRoll.push_back(message);
};

/// Wrapper for dumping const chars
void LogField::Dump(const char* message) {
	Dump(String(message));
};

void LogField::Append(const String& message) {
	// take last string and extend it
	*mLogRoll.rbegin() += message;
}

void LogField::Clear() {
	// erase the log roll
	mLogRoll.clear();
}

void LogField::Paint(float d) {
	Widget::Paint(d);
	
	// Draw lines of text
	OVERLAY_NEXT;
	glPushMatrix();
	{
		std::vector<String>::iterator 	i;
		
		for (i = mLogRoll.begin(); i != mLogRoll.end(); i++) {
			mFont->Value->DrawText(*i, getRealSize(), IFont::ALIGN_LEFT);
			glTranslatef(0, -16, 0);
		}
	}
	glPopMatrix();
};


/**** Image ****/

Widgets::Image::Image(const WtfEngine::Texture::tCacheRef& rTex)
: Widget("Image"), mrTexture(rTex)
{
	setPadding(IWidgetLayout::tPadding());
};

void Widgets::Image::Paint(float d) {
	glColor4f(1.0, 1.0, 1.0, 1.0);
	if(typeid(*mrTexture) != typeid(WtfEngine::FBOTexture)) {
		glScalef(1.0, -1.0, 1.0);
	} else {
		glTranslatef(0, -getInnerSize()[1], 0);
	};

	mrTexture->Draw(getInnerSize());
};



////// 
////// UTILS
////// 

using namespace WtfGui::Utils;


StringBuffer::StringBuffer(int MAX = 100) {
	this->max 	 = MAX;
	this->cursor = 0;
	this->gap	 = 0;
	this->len	 = 0;
	this->buf 	 = new char[MAX];
	this->text	 = new char[1];
};

StringBuffer::~StringBuffer() {
	delete [] buf;
	delete [] text;
};

void StringBuffer::moveGap(int pos) {
	if (pos < gap)
		for (int i = gap; i > pos; i--) buf[max - len + i - 1] = buf[i - 1];
	else if (pos > gap)
		for (int i = gap; i < pos; i++) buf[i] = buf[max - len + i];
	gap = pos;
};

char StringBuffer::charAt(int pos) {
	if (pos < gap) {
		return buf[pos];
	} else if (pos < len) {
		return buf[max - len + pos];
	} else {
		throw new std::runtime_error("charAt: out of bounds!");
	};
};

char StringBuffer::getChar() {
	return charAt(cursor);
};

void StringBuffer::insertChar(char ch) {
	moveGap(cursor);
	buf[gap] = ch;
	gap++;
	len++;
	cursor++;
};

/// \param direction 0 = left (BACKSPACE), 1 = right (DEL). Default is 0
void StringBuffer::deleteChar(int direction) {
	if (direction == 0) {
		if (len > 0 && cursor > 0) {
			moveGap(cursor);
			gap--;
			len--;
			cursor--;
		}
	} else {
		if (len > 0 && cursor < len) {
			moveGap(cursor);
			len--;
		}
	}
};

/// \param direction -1 = left; 1 = right;
void StringBuffer::moveCursor(int direction) {
	cursor += direction;
	if (cursor < 0) cursor = 0;
	if (cursor > len) cursor = len;
};

/// TODO: No check whether the text fits in the buffer or not
void StringBuffer::setText(const String& text) {
	len = strlen(text.c_str());
	cursor = len;
	gap = len;
	
	// for (int i = 0; i < len; i++) buf[i] = text[i];
	memcpy(buf, text.c_str(), len);
};

/// \param withCursor if true, toString will also add '_' to where the cursor is. Default is false
tString StringBuffer::toString(bool withCursor) {
	// Remember: text =  buf[0..gap) ++ buf[MAX-len + gap..MAX]
	int sh = (withCursor) ? 1 : 0;
	text = (char*) realloc(text, (len + 1 + sh) * sizeof(char));
	
	// place cursor
	if (withCursor) {
		moveGap(cursor);
		text[gap] = '_';
	}
	
	// for (i = 0; i < gap; i++) text[i] = buf[i];
	memcpy(text, buf, gap);
	// for (int j = MAX - len + gap; j < MAX; j++, i++) text[i] = buf[j];
	memcpy(text + gap + sh, buf + max - len + gap, len - gap);
	
	// end string and return
	text[len + sh] = '\0';
	return text;
};